package com.leizhi.demo.monitor.achieve;

import com.leizhi.demo.monitor.cache.DeviceCache;
import com.leizhi.demo.monitor.cache.NonceCache;
import com.leizhi.demo.monitor.achieve.listener.SipListenerPoint;
import com.leizhi.demo.monitor.pojo.*;
import com.leizhi.demo.monitor.utils.SipContentHelperUtil;
import com.leizhi.demo.monitor.utils.SipUtil;
import gov.nist.javax.sip.SipStackImpl;
import gov.nist.javax.sip.header.Expires;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import javax.sip.*;
import javax.sip.address.Address;
import javax.sip.address.AddressFactory;
import javax.sip.address.SipURI;
import javax.sip.header.*;
import javax.sip.message.MessageFactory;
import javax.sip.message.Request;
import javax.sip.message.Response;
import java.io.ByteArrayInputStream;
import java.text.ParseException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

import static com.leizhi.demo.monitor.utils.SipConstantUtil.*;

/**
 * @Description SIP 服务
 * @Version 1.0.0-SNAPSHOT
 * @Author leizhi
 * @Date 2025/4/27 9:58
 */
public class SipService {

    private static final Logger logger = LoggerFactory.getLogger(SipService.class);

    private SipProvider mUdpSipProvider;
    private SipProvider mTcpSipProvider;
    private AddressFactory mAddressFactory;
    private HeaderFactory mHeaderFactory;
    private MessageFactory mMessageFactory;
    /* 响应检查，同步等待器 */
    private Map<String, SyncFuture<Dialog>> respondCheck;
    /* 摘要认证 */
    private AuthenticationHelper authenticationHelper;

    @Autowired
    private SipDispChange sipDispChange;
    @Autowired
    private DeviceCache deviceCache;
    @Autowired
    private NonceCache nonceCache;


    /* 初始化 SIP 服务 */
    public boolean initSip(SipDispChange sipDispChange) {
        this.respondCheck = new ConcurrentHashMap<>();
        try {
            // 创建SipFactory实例，SipFactory是用于生成SIP堆栈和消息的工厂类，定义基本参数
            SipFactory sipFactory = SipFactory.getInstance();
            Properties properties = new Properties();
            properties.setProperty(STACK_NAME, "GB28181_SIP");
            properties.setProperty(IP_ADDRESS, sipDispChange.getSipIp());
            SipStackImpl mSipStack = (SipStackImpl) sipFactory.createSipStack(properties);

            // // 创建并初始化 SIP 工厂
            mHeaderFactory = sipFactory.createHeaderFactory();
            mAddressFactory = sipFactory.createAddressFactory();
            mMessageFactory = sipFactory.createMessageFactory();
            authenticationHelper = new AuthenticationHelper();
            SipListenerPoint sipListenerPoint = new SipListenerPoint(this);

            // 创建 UDP 监听点
            ListeningPoint udpListeningPoint = mSipStack.createListeningPoint(sipDispChange.getSipIp(), sipDispChange.getSipPort(), UDP);
            mUdpSipProvider = mSipStack.createSipProvider(udpListeningPoint);
            mUdpSipProvider.addSipListener(sipListenerPoint);

            // 创建 TCP 监听点
            ListeningPoint tcpListeningPoint = mSipStack.createListeningPoint(sipDispChange.getSipIp(), sipDispChange.getSipPort(), TCP);
            mTcpSipProvider = mSipStack.createSipProvider(tcpListeningPoint);
            mTcpSipProvider.addSipListener(sipListenerPoint);
            return true;
        } catch (Exception e) {
            logger.error("SIP 服务启动异常：{}", e.getMessage());
            return false;
        }
    }

    /* 添加同步响应信息 */
    public void putRespondCheck(String callId, SyncFuture<Dialog> syncFuture) {
        respondCheck.put(callId, syncFuture);
    }

    /* 处理注册请求 */
    public void processRegister(RequestEvent requestEvent, String signalingProtocol) throws Exception {
        ServerTransaction serverTransaction = getServerTransaction(requestEvent, signalingProtocol);
        Request request = requestEvent.getRequest();

        //未携带授权头或者密码错误 回复401
        AuthorizationHeader authHeader = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME);
        if (authHeader == null || !nonceCache.exist(authHeader.getNonce()) || !authenticationHelper.doAuthenticate(request, sipDispChange.getSipPassword())) {
            logger.info("processRegister >>> 401，未带授权头或者密码错误");
            Response response = mMessageFactory.createResponse(Response.UNAUTHORIZED, request);
            response.addHeader(getAuthenticateHeader());
            sendResponse(response, serverTransaction);
            return;
        }

        // 回复200，并携带头部信息
        Response response = mMessageFactory.createResponse(Response.OK, request);
        response.addHeader(mHeaderFactory.createDateHeader(Calendar.getInstance(Locale.ENGLISH)));
        ExpiresHeader expiresHeader = (ExpiresHeader) request.getHeader(Expires.NAME);
        response.addHeader(request.getHeader(ContactHeader.NAME));
        response.addHeader(request.getExpires());
        sendResponse(response, serverTransaction);

        // 判断是否为注销信息，如果不是则记录设备信息
        if (expiresHeader == null || expiresHeader.getExpires() != 0) {
            String deviceId = SipUtil.getDeviceId((FromHeader) request.getHeader(FromHeader.NAME));
            Device device = deviceCache.select(deviceId);
            if (device == null) {
                // 如果设备不存在，则生成默认信息
                device = new Device(deviceId, signalingProtocol);
                device.setFlowProtocol(TCP);
            }

            Host host = SipUtil.getHost((ViaHeader) request.getHeader(ViaHeader.NAME));
            device.setHost(host);

            sendCatalog(device, String.valueOf(1));
            deviceCache.add(device.getDeviceId(), device);
            logger.info("收到来自设备【{}】的注册消息，设备信息：{}", device.getDeviceId(), device);
            return;
        }
        logger.warn("收到设备注销信息：{}", request);
    }

    /* 处理消息请求 */
    public void processMessage(RequestEvent requestEvent, String protocol) throws Exception {
        // 解析xml格式的信息
        Request request = requestEvent.getRequest();
        SAXReader reader = new SAXReader();
        Document xml = reader.read(new ByteArrayInputStream(request.getRawContent()));

        // 获取xml消息中的设备id
        Element rootElement = xml.getRootElement();
        Element deviceIdElement = rootElement.element(ELEMENT_DEVICE_ID);
        if (deviceIdElement == null) {
            return;
        }

        String deviceId = deviceIdElement.getText();
        String cmd = rootElement.element(CMD_TYPE).getStringValue();
        if (MESSAGE_KEEP_ALIVE.equals(cmd)) {   // 心跳信息
            if (!deviceCache.exist(deviceId)) { // 判断设备是否存在，不存在则发送拒绝响应
                Response response = mMessageFactory.createResponse(Response.BAD_REQUEST, request);
                sendResponse(response, getServerTransaction(requestEvent, protocol));
                return;
            }
        } else if (MESSAGE_CATALOG.equals(cmd)) {   // 目录信息
            Element deviceListElement = rootElement.element(ELEMENT_DEVICE_LIST);
            if (deviceListElement == null) {
                return;
            }
            Iterator<Element> deviceListIterator = deviceListElement.elementIterator();
            if (deviceListIterator == null) {
                return;
            }

            Device device = deviceCache.select(deviceId);
            Map<String, DeviceChannel> channelMap = device.getChannelMap();
            while (deviceListIterator.hasNext()) {
                Element itemDevice = deviceListIterator.next();
                Element channelDeviceElement = itemDevice.element(ELEMENT_DEVICE_ID);
                if (channelDeviceElement == null) {
                    continue;
                }

                String channelDeviceId = channelDeviceElement.getText();
                Element channdelNameElement = itemDevice.element(ELEMENT_NAME);
                String channelName = channdelNameElement != null ? channdelNameElement.getText() : "IS_NOT_SET";

                DeviceChannel deviceChannel = channelMap.containsKey(channelDeviceId) ? channelMap.get(channelDeviceId) : new DeviceChannel();
                deviceChannel.setName(channelName);
                deviceChannel.setDeviceId(channelDeviceId);
                deviceChannel.setStatus(0);
                channelMap.put(channelDeviceId, deviceChannel);
                if (device.getAcquiesceChannel() == null) {
                    device.setAcquiesceChannel(channelDeviceId);
                }
            }
            deviceCache.add(deviceId, device);
        }

        // 回复200，代表成功并保活心跳
        Response response = mMessageFactory.createResponse(Response.OK, request);
        sendResponse(response, getServerTransaction(requestEvent, protocol));
    }

    /* 处理 BYE 请求 */
    public void processBye(RequestEvent requestEvent, String protocol) throws Exception {
        Request request = requestEvent.getRequest();
        ServerTransaction serverTransaction = requestEvent.getServerTransaction();
        if (serverTransaction == null || serverTransaction.getDialog() == null) {
            serverTransaction = getSipProvider(protocol).getNewServerTransaction(request);
        }

        Response response = mMessageFactory.createResponse(Response.OK, request);
        serverTransaction.sendResponse(response);
    }

    /* 处理 ACK 请求 */
    public void processAck(ResponseEvent responseEvent) throws Exception {
        ClientTransaction clientTransaction = responseEvent.getClientTransaction();
        if (clientTransaction == null) {
            return;
        }

        Response response = responseEvent.getResponse();
        CSeqHeader clientCSeqHeader = (CSeqHeader) response.getHeader(CSeqHeader.NAME);

        long seqId = clientCSeqHeader.getSeqNumber();
        Dialog clientDialog = clientTransaction.getDialog();
        Request ackRequest = clientDialog.createAck(seqId);

        SipURI requestURI = (SipURI) ackRequest.getRequestURI();
        ViaHeader viaHeader = (ViaHeader) response.getHeader(ViaHeader.NAME);
        requestURI.setHost(viaHeader.getHost());
        requestURI.setPort(viaHeader.getPort());
        clientDialog.sendAck(ackRequest);

        CallIdHeader callIdHeader = (CallIdHeader) ackRequest.getHeader(CallIdHeader.NAME);
        SyncFuture<Dialog> syncFuture = respondCheck.get(callIdHeader.getCallId());
        if (syncFuture != null) {
            syncFuture.setResponse(clientDialog);
        }
    }

    /* 发送获取目录信息 */
    public void sendCatalog(Device device, String sn) throws Exception {
        Host host = device.getHost();
        String callId = SipUtil.uuid();
        String fromTag = SipUtil.uuid();
        String deviceId = device.getDeviceId();

        Request request = createRequest(deviceId, host.getAddress(), host.getWanIp(), host.getWanPort(), device.getSignalingProtocol(),
                sipDispChange.getSipId(), sipDispChange.getSipRealm(), fromTag, deviceId, sipDispChange.getSipRealm(), callId,
                1L, Request.MESSAGE);
        String catalogContent = SipContentHelperUtil.generateCatalogContent(deviceId, sn);
        ContentTypeHeader contentTypeHeader = mHeaderFactory.createContentTypeHeader("Application", "MANSCDP+xml");
        request.setContent(catalogContent, contentTypeHeader);
        request.addHeader(contentTypeHeader);

        ClientTransaction clientTransaction = getSipProvider(device.getSignalingProtocol()).getNewClientTransaction(request);
        clientTransaction.sendRequest();
    }

    /* 发送实时流媒体请求 */
    public void sendInvite(Device device, String callId, String content) throws ParseException, InvalidArgumentException, SipException {
        Host host = device.getHost();
        String channelId = device.getAcquiesceChannel();
        String fromTag = SipUtil.uuid();
        String realm = channelId.substring(0, 8);

        Request request = createRequest(channelId, host.getAddress(), host.getWanIp(), host.getWanPort(), device.getSignalingProtocol(),
                sipDispChange.getSipId(), sipDispChange.getSipRealm(), fromTag, channelId, realm, callId, 20L, Request.INVITE);

        String address = sipDispChange.getSipIp().concat(":").concat(String.valueOf(sipDispChange.getSipPort()));
        SipURI fromUserInfo = mAddressFactory.createSipURI(sipDispChange.getSipId(), address);
        Address concatAddress = mAddressFactory.createAddress(fromUserInfo);
        request.addHeader(mHeaderFactory.createContactHeader(concatAddress));

        ContentTypeHeader contentTypeHeader = mHeaderFactory.createContentTypeHeader("Application", "SDP");
        request.setContent(content, contentTypeHeader);

        ClientTransaction clientTransaction = getSipProvider(device.getSignalingProtocol()).getNewClientTransaction(request);
        clientTransaction.sendRequest();
    }

    /* 发送 BYE 请求 */
    public void sendBye(PushStreamInfo stream) {
        try {
            Dialog dialog = stream.getDialog();
            if (dialog == null) {
                return;
            }

            Request byeRequest = dialog.createRequest(Request.BYE);
            ClientTransaction clientTransaction = getSipProvider(stream.getProtocol()).getNewClientTransaction(byeRequest);
            dialog.sendRequest(clientTransaction);
        } catch (SipException e) {
            logger.error("发送 BYE 消息失败，错误：{}", e.getMessage());
        }
    }

    /* 回复响应 */
    private void sendResponse(Response response, ServerTransaction serverTransaction) throws Exception {
        serverTransaction.sendResponse(response);
    }

    /* 获取服务端事务 */
    private ServerTransaction getServerTransaction(RequestEvent requestEvent, String SignalingProtocol) throws Exception {
        ServerTransaction serverTransaction = requestEvent.getServerTransaction();
        if (serverTransaction == null) {
            Request request = requestEvent.getRequest();
            serverTransaction = getSipProvider(SignalingProtocol).getNewServerTransaction(request);
        }
        return serverTransaction;
    }

    /* 创建请求 */
    private Request createRequest(String deviceId, String address, String targetIp, int targetPort, String protocol, String fromUserInfo,
                                  String fromHostPort, String fromTag, String toUserInfo, String toHostPort, String callId, Long seqNo,
                                  String method) throws ParseException, InvalidArgumentException {
        SipURI requestLine = mAddressFactory.createSipURI(deviceId, address);

        List<ViaHeader> viaHeaderList = new ArrayList<>();
        ViaHeader viaHeader = mHeaderFactory.createViaHeader(targetIp, targetPort, protocol, null);
        viaHeader.setRPort();
        viaHeaderList.add(viaHeader);

        SipURI toAddress = mAddressFactory.createSipURI(toUserInfo, toHostPort);
        Address toNameAddress = mAddressFactory.createAddress(toAddress);
        ToHeader toHeader = mHeaderFactory.createToHeader(toNameAddress, null);

        SipURI from = mAddressFactory.createSipURI(fromUserInfo, fromHostPort);
        Address fromNameAddress = mAddressFactory.createAddress(from);
        FromHeader fromHeader = mHeaderFactory.createFromHeader(fromNameAddress, fromTag);

        CallIdHeader callIdHeader = getSipProvider(protocol).getNewCallId();
        callIdHeader.setCallId(callId);

        CSeqHeader cSeqHeader = mHeaderFactory.createCSeqHeader(seqNo, method);

        MaxForwardsHeader maxForwardsHeader = mHeaderFactory.createMaxForwardsHeader(70);
        return mMessageFactory.createRequest(requestLine, method, callIdHeader, cSeqHeader, fromHeader, toHeader,
                viaHeaderList, maxForwardsHeader);
    }

    /* 生成 WWWAuthenticateHeader */
    private WWWAuthenticateHeader getAuthenticateHeader() throws ParseException {
        String nonce = SipUtil.uuid();
        nonceCache.add(nonce, "MD5");

        WWWAuthenticateHeader authHeader = mHeaderFactory.createWWWAuthenticateHeader("Digest");
        authHeader.setRealm(sipDispChange.getSipRealm());
        authHeader.setNonce(nonce);
        authHeader.setAlgorithm("MD5");
        return authHeader;
    }

    /* 获取 SipProvider */
    private SipProvider getSipProvider(String protocol) {
        if (protocol.equals(UDP)) {
            return mUdpSipProvider;
        } else if (protocol.equals(TCP)) {
            return mTcpSipProvider;
        } else {
            throw new RuntimeException("协议类型错误");
        }
    }

}
